<?php
/**
 * @license http://www.gnu.org/licenses/gpl.html GNU GPL, version 3 or later
 * @package FrontPress
 * @subpackage Route
 */

/**
 * Extend this class with methods for each page, then use
 * {@link FP_Router::add()} to add routes to the router.
 *
 * @since   2010-07-27-r20
 * @version $Id: class.fp-route.php 40 2010-07-31 22:37:01Z Nightgunner5 $
 */
abstract class FP_Route {
	var $errors = array();
	var $notices = array();
	var $request_running = false;
	var $template_path = null;

	var $router = null;
	var $fake_request = false;
	var $exited = false;
	var $exit_message;
	var $redirected = false;
	var $redirected_to = null;
	var $rendered_template = false;
	var $loaded_template = null;
	var $template_output = null;
	var $headers = array();

	private $sent_404 = false;

	/**
	 * Print an error message and exit. The error message is formatted using
	 * {@link backpress_die()}, which makes the page into a white box with
	 * rounded corners.
	 *
	 * @param string $message the message to be shown to the user
	 * @param int $status the HTTP status code to return. Default is 500.
	 * @param string title the (optional) title for the error page.
	 * @uses backpress_die()
	 */
	final function die_with_error( $message, $status = 500, $title = '' ) {
		if ( $this->fake_request ) {
			$this->exited = true;
			$this->exit_message = $message;
		}
		backpress_die( $message, $title, array( 'response' => $status ) );
	}

	final function before_request() {
		do_action( 'before_request', get_class( $this ), $this->last_method_called );
	}

	final function after_request() {
		// we can't unregister a shutdown function
		// this check prevents this method from being run twice
		if ( !$this->request_running ) return;

		if ( !headers_sent() ) {
			$this->set_notices_and_errors();
		}

		do_action( 'after_request', get_class( $this ), $this->last_method_called );
	}

	/**
	 * Set an error and redirect to a URL. This is a convenience method.
	 *
	 * @param string $message The error message to send to the user
	 * @param string $url The page to redirect to (See {@link FP_Route::redirect()})
	 * @uses FP_Route::redirect()
	 * @see FP_Notice::add()
	 */
	final function redirect_with_error( $message, $url = null ) {
		$this->errors[] = $message;
		$this->redirect( $url );
	}

	/**
	 * Redirect the user to a different page. You must return from your
	 * route function after calling this.
	 *
	 * @param string $url The URL to redirect to. The user will be sent to
	 *                    the referring page if this is not given.
	 */
	final function redirect( $url = null ) {
		if ( $this->fake_request ) {
			$this->redirected = true;
			$this->redirected_to = $url;
			return;
		}

		$this->set_notices_and_errors();
		if ( is_null( $url ) )
			$url = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : frontpress_uri();
		$this->header( 'Location: ' . $url );
		$this->tmpl( 'redirect', compact( 'url' ) );
	}

	/**
	 * Send headers required for browsers to download the page as a file.
	 *
	 * @param string $filename The name of the file being sent
	 */
	final function headers_for_download( $filename ) {
		$this->header( 'Content-Description: File Transfer' );
		$this->header( 'Pragma: public' );
		$this->header( 'Expires: 0' );
		$this->header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' );
		$this->header( 'Content-Disposition: attachment; filename=' . $filename );
		$this->header( 'Content-Type: application/octet-stream', true );
		$this->header( 'Connection: close' );
	}

	/**
	 * Send the notices and errors described in {@link FP_Route::$notices}
	 * and {@link FP_Route::$errors}. It is generally best to call
	 * {@link FP_Notice::add()} directly instead of using these variables.
	 * This function is included only for backward compatability.
	 *
	 * @see FP_Notice::add()
	 */
	final function set_notices_and_errors() {
		if ( $this->fake_request ) return;

		foreach( $this->notices as $notice ) {
			FP_Notice::add( $notice );
		}
		$this->notices = array();

		foreach( $this->errors as $error ) {
			FP_Notice::add( $error, 'error' );
		}
		$this->errors = array();
	}

	/**
	 * Loads a template.
	 *
	 * @param string $template template name to load
	 * @param array $args Associative array with arguements, which will be exported in the template PHP file
	 */
	final function tmpl( $template, $args = array() ) {
		if ( $this->fake_request ) {
			$this->rendered_template = true;
			$this->loaded_template = $template;
		}
		$this->set_notices_and_errors();
		$this->header( 'Content-Type: text/html; charset=' . backpress_get_option( 'charset' ) );

		return frontpress_tmpl_load( $template, $args, $this->template_path );
	}

	/**
	 * Execute the 404 route associated with this route's router.
	 * This method stops execution after running.
	 */
	final function tmpl_404() {
		if ( !$this->sent_404 ) {
			$this->sent_404 = true;
			$this->router->send_404();
		}
		exit;
	}

	/**
	 * Stop execution with an unformatted error message.
	 *
	 * @param string $message the message to exit with
	 * @deprecated use {@link FP_Route::die_with_error()}
	 */
	final function exit_( $message = '' ) {
		if ( $this->fake_request ) {
			$this->exited = true;
			$this->exit_message = $message;
		}
		exit( $message );
	}

	/**
	 * Send a header to the client. Use this method instead of
	 * PHP {@link header()} to allow for unit tests.
	 *
	 * @param string $string the header in "Key: Value" format
	 */
	final function header( $string ) {
		if ( $this->fake_request ) {
			list( $header, $value ) = explode( ':', $string, 2 );
			$this->headers[$header] = $value;
		} else {
			header( $string );
		}
	}

	/**
	 * Send a status header to the client.
	 *
	 * @param int $status the HTTP status code
	 */
	final function status_header( $status ) {
		if ( $this->fake_request ) {
			$this->http_status = $status;
			return;
		}
		return status_header( $status );
	}
}